/*
 * Copyright 2011 David Crosson
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
 
package fr.janalyse.series

import math._
import collection.immutable.TreeSet
import scala.collection.immutable.TreeMap



case class LinearApproximation(initial:Double=0, slope:Double=0) {
  def secondSlope=slope*1000
  def minuteSlope=slope*1000*60
  def hourSlope=slope*1000*3600
  def daySlope=slope*1000*3600*24
}

/**
  * Statistics datastore
  * @author David Crosson
  */
case class Statistics(
    count:Long,
    min:Double,
    max:Double,
    avg:Double,
    weightAvg:Double,       // Moyenne pondérée par le temps séparant cette cellule de la suivante
    sum:Double,
    sd:Double,
    scaleFactor:Double,
    range:Double,           // max - min
    delta:Double,           // close - open
    open:Double,
    close:Double,
    percentile90:Double,
    alive:Long,             // lastTime - firstTime
    linearApproximation:LinearApproximation
)


/**
  * Series statistics data computation
  * @author David Crosson
  */
object Statistics {
  def apply(series:Series[Cell]) = {
    var min=0d
    var max=0d
    var count=0L
    var sum=0d
    var avg=0d
    var weightAvg=0d
    var previousCell:Option[Cell]=None
    var openCell:Option[Cell]=None
    var first=true

    /*case class PercentileValueContainer(value:Double=0d, count:Int=0)

    implicit val percentileValueContainerOrdering = new Ordering[PercentileValueContainer] {
      def compare(x:PercentileValueContainer, y:PercentileValueContainer) = (x.value compareTo y.value)
    }

    var orderedPercentileValueContainers = new TreeSet[PercentileValueContainer]()
    */
    var orderedPercentileValueContainers = new TreeMap[Double, Int]()
    

    series foreach {c => c match {
        case cell:StatCell if first =>
          first = false
          min   = cell.min
          max   = cell.max
          count = cell.count
          sum   = cell.sum
          avg   = cell.value
          weightAvg = cell.value
          openCell = Some(cell)

        case cell:StatCell =>
          if (min > cell.min) min=cell.min
          if (max < cell.max) max=cell.max
          count+=cell.count
          sum+=cell.sum
          avg = (avg*(count-cell.count)/count + cell.value*cell.count/count)
          val prevTotWeight = previousCell.get.time - openCell.get.time
          val curTotWeight  = cell.time - openCell.get.time
          val curWeight     = cell.time - previousCell.get.time
          weightAvg = weightAvg*prevTotWeight/curTotWeight + cell.value*curWeight/curTotWeight

        // ----------------------------------------------------
        case cell if first =>
          first=false
          min   = cell.value
          max   = cell.value
          count = 1
          sum   = cell.value
          avg   = cell.value
          weightAvg = cell.value
          openCell = Some(cell)

        case cell =>
          if (min > cell.value) min=cell.value
          if (max < cell.value) max=cell.value
          count+=1
          sum+=cell.value
          avg = avg*(count-1)/count + cell.value/count
          val prevTotWeight = previousCell.get.time - openCell.get.time
          val curTotWeight  = cell.time - openCell.get.time
          val curWeight     = cell.time - previousCell.get.time
          weightAvg = weightAvg*prevTotWeight/curTotWeight + cell.value*curWeight/curTotWeight
      }
      /*
      val f = orderedPercentileValueContainers find {x => x.value == c.value} match {
        case Some(e) => PercentileValueContainer(c.value, e.count+1)
        case None => PercentileValueContainer(c.value,1)
      }
      orderedPercentileValueContainers += f
      */
      orderedPercentileValueContainers += c.value -> (orderedPercentileValueContainers.getOrElse(c.value, 0) + 1)
      
      previousCell=Some(c)
    }
    var closeCell=previousCell

    var n=((count-1)*90/100)+1
    var percentile90 = 0d;
    for( (value,count) <- orderedPercentileValueContainers if (n>0)) {
      //n -= pvc.count
      //percentile90 = pvc.value
      n -= count
      percentile90 = value
    }
    
    val alive = closeCell map {_.time - openCell.get.time} getOrElse 0L

    val (linearApproximation, sd) = computeLinearRegressionAndStandardDeviation(series,avg)
    
    
    new Statistics(
      count=count,
      min=min,
      max=max,
      sum=sum,
      avg=avg,
      weightAvg=weightAvg,
      range=max-min,
      delta = closeCell.map(_.value).getOrElse(0d) - openCell.map(_.value).getOrElse(0d),
      open  = openCell.map(_.value).getOrElse(0d),
      close = closeCell.map(_.value).getOrElse(0d),
      percentile90=percentile90,
      alive=alive,
      sd=sd,
      linearApproximation=linearApproximation,
      scaleFactor=0d
    )
  }
  
  
  private def computeLinearRegressionAndStandardDeviation(series:Series[Cell], avg:Double):Tuple2[LinearApproximation,Double]= {
      if (series.size==0)  return  (LinearApproximation(), 0d)
       
      // DONE : Better precision because the time is a big value, as it is the ellapsed ms since seventies !
      val mc = new java.math.MathContext(50, java.math.RoundingMode.HALF_DOWN)
      
      var sumxx = BigDecimal(0)
      var sumyy = BigDecimal(0)
      var sumxy = BigDecimal(0)
      var sumx = BigDecimal(0)
      var sumy = BigDecimal(0)
      
      var sumdiff = 0d
      
      for(cell <- series) {
        val x = BigDecimal(cell.time)
        val y = BigDecimal(cell.value)
        sumx += x
        sumy += y
        sumxx += x*x
        sumyy += y*y
        sumxy += x*y
        sumdiff += (cell.value-avg)*(cell.value-avg)
      }

      val sd = if (series.size>1) sqrt(sumdiff/(series.size-1.0d)) else 0d
      
      val n = BigDecimal(series.size.toDouble);

      val Sxx = sumxx-(sumx*sumx)(mc)/n;
      val Syy = sumyy-(sumy*sumy)(mc)/n;
      val Sxy = sumxy-(sumx*sumy)(mc)/n;

      if (Sxx.toDouble==0d) return (LinearApproximation(),sd)

      val bigB = Sxy(mc)/Sxx
      val bigA = (sumy-bigB*sumx)(mc)/n

      (LinearApproximation(bigA.toDouble,bigB.toDouble),sd)
  }
}



